/**
 * Copyright 2019 吉鼎科技.
 *
 * <p>
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * <p>
 * http://www.apache.org/licenses/LICENSE-2.0
 * <p>
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package cn.easyplatform.services.project;

import bsh.EvalError;
import cn.easyplatform.EasyPlatformWithLabelKeyException;
import cn.easyplatform.EntityNotFoundException;
import cn.easyplatform.ServiceException;
import cn.easyplatform.cfg.*;
import cn.easyplatform.cfg.bd.StdBusinessDateHandlerFactory;
import cn.easyplatform.cfg.id.StdIdGeneratorFactory;
import cn.easyplatform.cfg.tx.jdbc.JdbcTransactionContextFactory;
import cn.easyplatform.compiler.Compiler;
import cn.easyplatform.compiler.impl.JdkCompiler;
import cn.easyplatform.contexts.Contexts;
import cn.easyplatform.contexts.ExecuteContext;
import cn.easyplatform.contexts.RecordContext;
import cn.easyplatform.dao.*;
import cn.easyplatform.dos.*;
import cn.easyplatform.entities.BaseEntity;
import cn.easyplatform.entities.EntityInfo;
import cn.easyplatform.entities.beans.LogicBean;
import cn.easyplatform.entities.beans.ResourceBean;
import cn.easyplatform.entities.beans.project.*;
import cn.easyplatform.entities.transform.TransformerFactory;
import cn.easyplatform.i18n.I18N;
import cn.easyplatform.interceptor.CommandContext;
import cn.easyplatform.lang.Lang;
import cn.easyplatform.lang.Strings;
import cn.easyplatform.lang.stream.StringOutputStream;
import cn.easyplatform.log.LogManager;
import cn.easyplatform.messages.vos.admin.*;
import cn.easyplatform.services.*;
import cn.easyplatform.services.system.DataSourceService;
import cn.easyplatform.spi.extension.ApplicationService;
import cn.easyplatform.spi.extension.ProjectContext;
import cn.easyplatform.type.*;
import cn.easyplatform.util.EntityUtils;
import cn.easyplatform.util.ObfuscatedString;
import cn.easyplatform.util.RuntimeUtils;
import com.rits.cloning.Cloner;
import org.apache.commons.lang3.LocaleUtils;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.cache.Cache;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.sql.DataSource;
import java.io.File;
import java.io.Serializable;
import java.lang.reflect.Constructor;
import java.util.*;
import java.util.concurrent.Callable;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Future;

/**
 * @author <a href="mailto:davidchen@epclouds.com">littleDog</a> <br/>
 * @since 2.0.0 <br/>
 */
public class ProjectService extends AbstractService implements IProjectService, ProjectContext {

    private static final Logger log = LoggerFactory.getLogger(new ObfuscatedString(new long[]{0x5569FFC441D0F486L, 0x6C9873561439FECDL, 0xA83D561FF476AE47L, 0x83F44CDC9A464B51L}).toString()); /* => "cn.easyplatform.a.b.c" */

    // //////////表id生成器//////////
    private IdGeneratorFactory idGeneratorFactory;
    // /////////事务管理工厂////////////////
    private TransactionContextFactory transactionContextFactory;
    // //////////工作流引擎工厂对象//////////
    private BpmEngineContextFactory bpmEngineFactory;
    // ////////////营业日处理//////////////////////
    private BusinessDateHandlerFactory businessDateHandlerFactory;
    // 项目信息
    private ProjectBean project;
    // 支持的语言
    private List<String> languages;
    // 计划任务
    private ScheduleHandlerImpl scheduleHandler;
    // 资源管理
    private ResourceHandlerImpl resourceHandler;
    // 参数管理接口
    private EntityHandlerImpl entityHandler;
    // 会话管理
    private SessionManagerImpl sessionManager;
    // 记录锁管理
    private StdRowLockProvider rowLockProvider;
    // 项目配置
    private ProjectConfig context;
    // 工作目录
    private String workPath;
    // 默认地区
    private Locale locale;
    // 自定义无状态函数
    private Map<String, Class<?>> commands;
    //数据源服务
    private Map<String, ApplicationService> services;
    //业务相关服务
    private IBizService bizService;
    //动态任务，由逻辑函数生成
    private ICustomJob customJob;

    // 调用setState(int state)的
    private String lockUserId;
    // 启动时间
    private Date startTime;

    public ProjectService(ProjectBean project) {
        this.project = project;
    }

    @Override
    public String getId() {
        return project.getId();
    }

    @Override
    public String getName() {
        return project.getName();
    }

    @Override
    public String getDescription() {
        return project.getDescription();
    }

    @Override
    public final void start() throws ServiceException {
        if (getState() != StateType.STOP)
            return;
        try {
            LogManager.startApp(engineConfiguration.getLogPath(), project.getId());
            if (log.isInfoEnabled())
                log.info(I18N.getLabel("easyplatform.sys.common.starting", project.getName()));
            services = new ConcurrentHashMap<String, ApplicationService>();
            // 先启动数据源
            List<EntityInfo> ds = engineConfiguration.getEntityDao().getModels(project.getId(), EntityType.DATASOURCE.getName());
            if (ds.isEmpty())
                throw new ServiceException(I18N.getLabel("platform.db.not.found", project.getName()));
            for (EntityInfo entity : ds) {
                BaseEntity bean = TransformerFactory.newInstance()
                        .transformFromXml(entity);
                ResourceBean rb = (ResourceBean) bean;
                AbstractService s = new DataSourceService(this, rb);
                s.setEngineConfiguration(engineConfiguration);
                s.start();
                if (entity.getStatus() != 'R')
                    engineConfiguration.getEntityDao().setModelStatus(entity.getId(), "R");
            }

            EntityDao dao = engineConfiguration.getEntityDao();
            for (DeviceMapBean dm : project.getDevices()) {
                if (dm.getLoginId() > 0) {
                    String content = (String) dao.selectObject("SELECT content FROM ep_model_ext_info WHERE id=? AND projectId=?", dm.getLoginId(), project.getId());
                    if (content == null)
                        throw new EasyPlatformWithLabelKeyException("app.page.not.found", project.getId(), dm.getLoginId());
                    dm.setLoginPage(content);
                }
                if (dm.getMainId() > 0) {
                    String content = (String) dao.selectObject("SELECT content FROM ep_model_ext_info WHERE id=? AND projectId=?", dm.getMainId(), project.getId());
                    if (content == null)
                        throw new EasyPlatformWithLabelKeyException("app.page.not.found", project.getId(), dm.getMainId());
                    dm.setMainPage(content);
                }
            }
            if (project.getPortlets() != null && !project.getPortlets().isEmpty()) {
                for (PortletBean pb : project.getPortlets()) {
                    for (DeviceMapBean dm : pb.getDevices()) {
                        if (dm.getLoginId() > 0) {
                            String content = (String) dao.selectObject("SELECT content FROM ep_model_ext_info WHERE id=? AND projectId=?", dm.getLoginId(), project.getId());
                            if (content == null)
                                throw new EasyPlatformWithLabelKeyException("app.page.not.found", project.getId(), dm.getMainId());
                            dm.setLoginPage(content);
                        }
                        if (dm.getMainId() > 0) {
                            String content = (String) dao.selectObject("SELECT content FROM ep_model_ext_info WHERE id=? AND projectId=?", dm.getMainId(), project.getId());
                            if (content == null)
                                throw new EasyPlatformWithLabelKeyException("app.page.not.found", project.getId(), dm.getMainId());
                            dm.setMainPage(content);
                        }
                    }
                }
            }
            if (project.getDevices().size() == 1) {
                DeviceMapBean mb = new DeviceMapBean();
                mb.setType(DeviceType.MOBILE.getName());
                mb.setLoginPage("<div></div>");
                mb.setMainPage("<div></div>");
                mb.setName("Mobile Phone");
                project.getDevices().add(mb);
            }
            workPath = engineConfiguration.getWorkspace() + getWorkspace();
            File folder = new File(workPath);
            if (!folder.exists())
                folder.mkdir();
            //基础服务需要在自定义服务之前启动
            // 计划任务处理
            scheduleHandler = new ScheduleHandlerImpl(engineConfiguration, this);
            // row lock
            rowLockProvider = new StdRowLockProvider(this);
            // session manager
            sessionManager = new SessionManagerImpl(this);
            init();
            engineConfiguration.addService(this);
            engineConfiguration.getEntityDao().setModelStatus(project.getId(), "R");
            //动态任务
            customJob = new CustomJobService(engineConfiguration, this);
            customJob.start();
            super.setState(StateType.START);
            startTime = new Date();
            lockUserId = null;
            if (log.isInfoEnabled())
                log.info(I18N.getLabel("easyplatform.sys.common.started", project.getName()));
        } catch (EasyPlatformWithLabelKeyException e) {
            LogManager.stopApp(project.getId());
            throw new ServiceException(I18N.getLabel(e.getMessage(), e.getArgs()));
        } catch (ServiceException ex) {
            LogManager.stopApp(project.getId());
            throw ex;
        } catch (Exception ex) {
            if (log.isDebugEnabled())
                log.error(I18N.getLabel("easyplatform.sys.common.fail", getName()), ex);
            LogManager.stopApp(project.getId());
            throw new ServiceException(I18N.getLabel("easyplatform.sys.common.fail", getName(), ex.getMessage()));
        } finally {
            LogManager.endRequest();
        }
    }

    @SuppressWarnings("unchecked")
    private void init() {
        // 多国语言
        languages = new ArrayList<String>();
        String[] ar = project.getLanguages().split(",");
        if (ar.length == 0)
            ar = new String[]{"zh_cn"};
        for (String s : ar)
            languages.add(s.trim().toLowerCase());
        // 设置空的登陆页面
        Map<String, DeviceMapBean> map = new HashMap<String, DeviceMapBean>();
        for (DeviceMapBean dm : project.getDevices())
            map.put(dm.getType(), dm);
        for (DeviceMapBean dm : project.getDevices()) {
            if (!dm.getType().equals(DeviceType.AJAX.getName()) && Strings.isBlank(dm.getLoginPage()))
                dm.setLoginPage(map.get(DeviceType.AJAX.getName()).getLoginPage());
        }
        if (project.getPortlets() != null && !project.getPortlets().isEmpty()) {
            for (PortletBean pb : project.getPortlets()) {
                for (DeviceMapBean dm : pb.getDevices()) {
                    if (Strings.isBlank(dm.getLoginPage()))
                        dm.setLoginPage(map.get(dm.getType()).getLoginPage());
                }
            }
        }
        locale = LocaleUtils.toLocale(ar[0]);
        if (project.getProperties() == null)
            project.setProperties(new HashMap<>());
        context = new ProjectConfig(engineConfiguration, this, project.getProperties());
        String className = null;
        try {
            // 工作流
            className = context.getString("bpmEngineContextFactory");
            if (Strings.isBlank(className))
                className = "cn.easyplatform.bpm.SnakerEngineContextFactory";
            Class<BpmEngineContextFactory> clazz1 = (Class<BpmEngineContextFactory>) Class.forName(className);
            bpmEngineFactory = clazz1.newInstance();

            // 工作日检查
            className = context.getString("businessDateHandlerFactory");
            if (Strings.isBlank(className))
                className = StdBusinessDateHandlerFactory.class.getName();
            Class<BusinessDateHandlerFactory> clazz2 = (Class<BusinessDateHandlerFactory>) Class.forName(className);
            businessDateHandlerFactory = clazz2.newInstance();

            // id生成器
            className = context.getString("idGeneratorFactory");
            if (Strings.isBlank(className))
                className = StdIdGeneratorFactory.class.getName();
            Class<IdGeneratorFactory> clazz3 = (Class<IdGeneratorFactory>) Class.forName(className);
            idGeneratorFactory = clazz3.newInstance();

            // 事务管理
            className = context.getString("transactionContextFactory");
            if (Strings.isBlank(className))
                className = JdbcTransactionContextFactory.class.getName();
            Class<TransactionContextFactory> clazz4 = (Class<TransactionContextFactory>) Class.forName(className);
            transactionContextFactory = clazz4.newInstance();
            // entity cache
            entityHandler = new EntityHandlerImpl(this);
            // resource cache
            resourceHandler = new ResourceHandlerImpl(this);
            //biz service
            bizService = new BizServiceImpl(this);
            // 自定义函数
            commands = new HashMap<>();
            if (project.getCommands() != null) {
                for (CommandBean cmd : project.getCommands()) {
                    CustomVo.Entry entry;
                    if (cmd.getClassName().contains("."))
                        entry = new CustomVo.Entry(cmd.getName(), cmd.getClassName(), cmd.getDescription());
                    else {
                        EntityInfo rb = getEntityHandler().getEntity(cmd.getClassName());
                        if (rb == null)
                            throw new EasyPlatformWithLabelKeyException("entity.not.found", SubType.CLASS.getName(), cmd.getClassName());
                        entry = new CustomVo.Entry(cmd.getName(), cmd.getClassName(), cmd.getDescription());
                        entry.setContent(rb.getContent());
                    }
                    createCommand(entry);
                }
            }
            // 自定义服务
            if (project.getServices() != null) {
                for (ServiceBean sb : project.getServices()) {
                    CustomVo.Entry entry = null;
                    if (sb.getClassName().contains("."))
                        entry = new CustomVo.Entry(sb.getClassName(), sb.getDescription());
                    else {
                        EntityInfo rb = getEntityHandler().getEntity(sb.getClassName());
//                        if (rb == null)
//                            throw new EasyPlatformWithLabelKeyException("entity.not.found", SubType.CLASS.getName(), sb.getClassName());
                        if (rb != null) {//不存在不报错
                            entry = new CustomVo.Entry(sb.getClassName(), sb.getDescription());
                            entry.setContent(rb.getContent());
                        }
                    }
                    if (entry != null) {
                        try {
                            createService(entry);
                        } catch (Exception e) {
                            if (log.isErrorEnabled()) {
                                if (e instanceof EasyPlatformWithLabelKeyException) {
                                    log.error("create service " + entry.getClassName() + ":{}", I18N.getLabel(e.getMessage(), ((EasyPlatformWithLabelKeyException) e).getArgs()));
                                } else
                                    log.error("create service " + entry.getClassName() + ":", e);
                            }
                        }
                    }
                }
            }
        } catch (ClassNotFoundException e) {
            throw new ServiceException("Class not found:" + className);
        } catch (EvalError evalError) {
            throw new ServiceException(evalError.getMessage());
        } catch (InstantiationException e) {
            throw new ServiceException("Could not create instance:" + className, e);
        } catch (IllegalAccessException e) {
            throw new ServiceException("Could not create instance:" + className, e);
        }
    }

    /**
     * 创建自定义函数
     *
     * @throws ClassNotFoundException
     * @throws EvalError
     */
    private void createCommand(CustomVo.Entry entry) throws ClassNotFoundException, EvalError {
        Class<?> clazz;
        if (entry.getClassName().contains("."))
            clazz = Lang.loadClass(entry.getClassName());
        else {//从参数加载
            Compiler compiler = new JdkCompiler();
            clazz = compiler.compile(entry.getContent(), Thread.currentThread().getContextClassLoader());
        }
        commands.put(entry.getName(), clazz);
    }

    /**
     * 创建自定义服务
     *
     * @throws Throwable
     */
    private ApplicationService createService(CustomVo.Entry entry) throws Exception {
        if (services.containsKey(entry.getClassName()))//名称一样
            throw new EasyPlatformWithLabelKeyException("easyplatform.spi.error3", entry.getClassName());
        Class<?> clazz;
        if (entry.getClassName().contains(".")) {//外部java class
            clazz = Lang.loadClass(entry.getClassName());
        } else {//从参数加载
            //Interpreter interpreter = new Interpreter();
            //clazz = (Class<?>) interpreter.eval(entry.getContent());
            Compiler compiler = new JdkCompiler();
            clazz = compiler.compile(entry.getContent(), Thread.currentThread().getContextClassLoader());
        }
        if (ApplicationService.class.isAssignableFrom(clazz)) {
            ApplicationService service = null;
            Constructor<ApplicationService>[] constructors = (Constructor<ApplicationService>[]) clazz.getConstructors();
            for (Constructor<ApplicationService> constructor : constructors) {
                if (constructor.getParameterTypes().length == 1 && constructor.getParameterTypes()[0].isAssignableFrom(ProjectContext.class)) {
                    service = constructor.newInstance(this);
                    break;
                }
                if (constructor.getParameterTypes().length == 0) {
                    service = constructor.newInstance();
                    // 动态注入系统变量
                    if (!RuntimeUtils.injectObject(service, this, ProjectContext.class))
                        throw new EasyPlatformWithLabelKeyException("easyplatform.spi.error1", entry.getClassName());
                    break;
                }
            }
            if (service == null)
                throw new EasyPlatformWithLabelKeyException("easyplatform.spi.error1", entry.getClassName());
            service.start();
            services.put(entry.getClassName(), service);
            return service;
        } else throw new EasyPlatformWithLabelKeyException("easyplatform.spi.error0", entry.getClassName());
    }

    @Override
    public void stop() throws ServiceException {
        if (getState() == StateType.STOP)
            return;
        if (log.isInfoEnabled())
            log.info(I18N.getLabel("easyplatform.sys.common.stopping", project.getName()));
        try {
            if (commands != null) {
                commands.clear();
                commands = null;
            }
            customJob.stop();
            //停止服务
            for (ApplicationService s : services.values())
                s.stop();
            scheduleHandler.destory();
            //sessionManager.destory();
            rowLockProvider.destory();
            bizService.destory();
            services.clear();
            resourceHandler.destory();
            entityHandler.destory();

            services = null;
            scheduleHandler = null;
            sessionManager = null;
            resourceHandler = null;
            entityHandler = null;
            rowLockProvider = null;
            context = null;
            customJob = null;
            engineConfiguration.removeService(this);
            super.setState(StateType.STOP);
            startTime = null;
            lockUserId = null;
            if (log.isInfoEnabled())
                log.debug(I18N.getLabel("easyplatform.sys.common.stopped", project.getName()));
            LogManager.stopApp(project.getId());
        } catch (Exception ex) {
            if (log.isErrorEnabled())
                log.error(I18N.getLabel("easyplatform.sys.common.stop.fail", getName(), ex.getMessage()));
        }
    }

    @Override
    public ApplicationService startService(String id) throws Exception {
        if (project.getServices() != null) {
            services.remove(id);
            for (ServiceBean sb : project.getServices()) {
                if (sb.getClassName().equals(id)) {
                    CustomVo.Entry entry;
                    if (sb.getClassName().contains("."))
                        entry = new CustomVo.Entry(sb.getClassName(), sb.getDescription());
                    else {
                        EntityInfo rb = getEntityHandler().getEntity(sb.getClassName());
                        if (rb == null)
                            throw new EasyPlatformWithLabelKeyException("entity.not.found", SubType.CLASS.getName(), sb.getClassName());
                        entry = new CustomVo.Entry(sb.getClassName(), sb.getDescription());
                        entry.setContent(rb.getContent());
                    }
                    return createService(entry);
                }
            }
        }
        return null;
    }

    @Override
    public void addService(ApplicationService service) {
        services.put(service.getId(), service);
    }

    @Override
    public void removeService(String id) {
        services.remove(id);
    }

    @Override
    public ApplicationService getService(String id) {
        return services.get(id);
    }

    @Override
    public Collection<ApplicationService> getServices() {
        return Collections.unmodifiableCollection(services.values());
    }

    @Override
    public Map<String, Object> getRuntimeInfo() {
        Map<String, Object> map = new HashMap<String, Object>();
        map.put("service.start.time", startTime);
        return map;
    }

    @Override
    public void validate(UserDo user, boolean check) {
        if (getState() == StateType.STOP)
            throw new EasyPlatformWithLabelKeyException("easyplatform.project.stopped");
        if (getState() == StateType.PAUSE && lockUserId != null) {
            if (!user.getId().equals(lockUserId))
                throw new EasyPlatformWithLabelKeyException("easyplatform.project.paused");
        }
    }

    @Override
    public List<String> getLanguages() {
        return languages;
    }

    @Override
    public DeviceMapBean getDeviceMap(String portlet, String deviceType) {
        if (Strings.isBlank(portlet) || project.getPortlets() == null || project.getPortlets().isEmpty()) {
            for (DeviceMapBean bean : project.getDevices()) {
                if (bean.getType().equals(deviceType))
                    return bean;
            }
        } else {
            for (PortletBean pb : project.getPortlets()) {
                if (pb.getName().equalsIgnoreCase(portlet)) {
                    for (DeviceMapBean bean : pb.getDevices())
                        if (bean.getType().equals(deviceType))
                            return bean;
                }
            }
        }
        return null;
    }

    @Override
    public PortletBean getPortlet(String portlet) {
        for (PortletBean pb : project.getPortlets()) {
            if (pb.getName().equalsIgnoreCase(portlet))
                return pb;
        }
        return null;
    }

    @Override
    public String getWorkspace() {
        return "/" + project.getId();
    }

    @Override
    public IResourceHandler getResourceHandler() {
        return resourceHandler;
    }

    @Override
    public IScheduleHandler getScheduleHandler() {
        return scheduleHandler;
    }

    @Override
    public String getWorkPath() {
        return workPath;
    }

    @Override
    public Locale getLocale() {
        return locale;
    }

    public BpmEngineContext getBpmEngine(CommandContext cc) {
        return bpmEngineFactory.getBpmEngine(cc);
    }

    public BusinessDateHandler getBusinessDateHandler(CommandContext cc) {
        return businessDateHandlerFactory.getBusinessDateHandler(cc);
    }

    public RowLockProvider getRowLockProvider(CommandContext cc) {
        rowLockProvider.setCommandContext(cc);
        return rowLockProvider;
    }

    public TransactionContext getTransactionContext(CommandContext cc) {
        return transactionContextFactory.openTransactionContext(cc);
    }

    public IdGenerator getIdGenerator() {
        return idGeneratorFactory
                .getIdGenerator(DaoFactory.createSeqDao(getDataSource()));
    }

    @Override
    public ISessionManager getSessionManager() {
        return sessionManager;
    }

    @Override
    public IEntityHandler getEntityHandler() {
        return entityHandler;
    }

    @Override
    public IBizService getBizService() {
        return bizService;
    }

    @Override
    public <T> Cache<Serializable, T> getCache(String name) {
        return engineConfiguration.getCacheManager().getCache(project.getId() + ":" + name);
    }

    @Override
    public void removeCache(String name) {
        engineConfiguration.getCacheManager().removeCache(project.getId() + ":" + name);
    }

    @Override
    public void refresh(String type) {
        if ("ENTITY".equals(type)) {
            synchronized (project) {
                if (getState() == StateType.START) {
                    try {
                        if (entityHandler != null)
                            entityHandler.refresh();
                    } catch (DaoException ex) {
                        if (log.isErrorEnabled())
                            log.error(I18N.getLabel(ex.getMessage(), ex.getArgs()), ex);
                    } catch (Exception ex) {
                        if (log.isErrorEnabled())
                            log.error("reresh", ex);
                    }
                }
            }
        } else {
            bizService.refresh(type);
        }
    }

    @Override
    public ProjectConfig getConfig() {
        return context;
    }

    @Override
    public Object getCommand(String name) {
        Class<?> clazz = commands.get(name);
        if (clazz != null) {
            try {
                return clazz.newInstance();
            } catch (Exception ex) {
                throw new EasyPlatformWithLabelKeyException("script.engine.cmd.no.create", name, clazz);
            }
        }
        return null;
    }

    @Override
    public BizDao getBizDao() {
        return DaoFactory.createBizDao((DataSource) services.get(project.getBizDb()));
    }

    @Override
    public BizDao getBizDao(String resourceId) {
        if (Strings.isBlank(resourceId))
            return DaoFactory.createBizDao((DataSource) services.get(project.getBizDb()));
        else {
            DataSource ds = (DataSource) services.get(resourceId);
            if (ds == null && SystemServiceId.SYS_ENTITY_DS.equals(resourceId))
                ds = (DataSource) engineConfiguration.getService(SystemServiceId.SYS_ENTITY_DS);
            if (ds == null)
                throw new EntityNotFoundException(EntityType.DATASOURCE.getName(), resourceId);
            return DaoFactory.createBizDao(ds);
        }
    }

    @Override
    public IdentityDao getIdentityDao() {
        return DaoFactory.createIdentityDao(getDataSource(project.getIdentityDb()));
    }

    @Override
    public String getHome() {
        return engineConfiguration.getHome();
    }

    @Override
    public void setState(int state) {
        // state不能是NORMAL
        sessionManager.setState(null, state);
        if (state == StateType.PAUSE)
            scheduleHandler.pauseAll();
        else
            scheduleHandler.resumeAll();
        for (ApplicationService service : services.values())
            service.setState(state);
        super.setState(state);
    }

    @Override
    public Date getStartTime() {
        return startTime;
    }

    @Override
    public ProjectBean getEntity() {
        return project;
    }

    @Override
    public void save(Object obj) {
        synchronized (project) {
            if (obj instanceof ProjectVo) {
                ProjectVo pv = (ProjectVo) obj;
                project.setName(pv.getName());
                project.setDescription(pv.getDesp());
                project.setAppContext(pv.getAppContext());
                project.setBizDb(pv.getBizDb());
                project.setIdentityDb(pv.getIdentityDb());
                project.setLanguages(pv.getLanguages());
                project.setMode(pv.getMode());
                project.setTheme(pv.getTheme());
                if (!project.getProperties().equals(pv.getProperties())) {
                    project.setProperties(new HashMap<>(pv.getProperties()));
                    entityHandler.destory();
                    resourceHandler.destory();
                    bizService.destory();
                    init();
                } else {
                    project.setProperties(new HashMap<>(pv.getProperties()));
                    // 多国语言
                    languages.clear();
                    String[] ar = project.getLanguages().split(",");
                    if (ar.length == 0)
                        ar = new String[]{"zh_cn"};
                    for (String s : ar)
                        languages.add(s.trim().toLowerCase());
                    locale = LocaleUtils.toLocale(ar[0]);
                }
                pv = null;
                save(project.getPortlets());
            } else if (obj instanceof TemplateVo) {
                TemplateVo tv = (TemplateVo) obj;
                List<DeviceMapVo> dms = tv.getDevices();
                for (int i = 0; i < project.getDevices().size(); i++) {
                    DeviceMapBean mb = project.getDevices().get(i);
                    for (int j = 0; j < dms.size(); j++) {
                        DeviceMapVo mv = dms.get(j);
                        if (mv.getType().equals(mb.getType())) {
                            mb.setLoginPage(mv.getLoginPage());
                            mb.setMainPage(mv.getMainPage());
                            mb.setName(mv.getName());
                            mb.setTheme(mv.getTheme());
                            break;
                        }
                    }
                }
                List<PortletBean> portlets = null;
                if (project.getPortlets() != null && !project.getPortlets().isEmpty()) {
                    portlets = new ArrayList<>(project.getPortlets());
                    project.getPortlets().clear();
                } else
                    project.setPortlets(new ArrayList<>());
                if (tv.getPortlets() != null && !tv.getPortlets().isEmpty()) {
                    for (PortletVo pv : tv.getPortlets()) {
                        if (!Strings.isBlank(pv.getName()))
                            project.getPortlets().add(EntityUtils.cast(pv));
                    }
                }
                save(portlets);
            } else if (obj instanceof CustomVo.Entry) {//自定义服务或函数
                CommandContext cc = Contexts.getCommandContext();
                try {
                    CustomVo.Entry entry = (CustomVo.Entry) obj;
                    if (!Strings.isBlank(entry.getName())) {//自定义函数
                        commands.remove(entry.getName());
                        if (entry.getFlag() == 'D') {
                            Iterator<CommandBean> itr = project.getCommands().iterator();
                            while (itr.hasNext()) {
                                CommandBean cmd = itr.next();
                                if (entry.getName().equals(cmd.getName())) {
                                    itr.remove();
                                    break;
                                }
                            }
                        } else {
                            createCommand(entry);
                            if (entry.getFlag() == 'U') {
                                for (CommandBean cmd : project.getCommands()) {
                                    if (cmd.getName().equals(entry.getName())) {
                                        cmd.setClassName(entry.getClassName());
                                        cmd.setDescription(entry.getDescription());
                                        break;
                                    }
                                }
                            } else {
                                CommandBean cmd = new CommandBean();
                                cmd.setName(entry.getName());
                                cmd.setClassName(entry.getClassName());
                                cmd.setDescription(entry.getDescription());
                                if (project.getCommands() == null)
                                    project.setCommands(new ArrayList<>());
                                project.getCommands().add(cmd);
                            }
                        }
                    } else {//自定义服务
                        if (entry.getFlag() == 'D') {
                            ApplicationService as = services.get(entry.getClassName());
                            if (as != null)
                                as.stop();
                            Iterator<ServiceBean> itr = project.getServices().iterator();
                            while (itr.hasNext()) {
                                ServiceBean sb = itr.next();
                                if (entry.getClassName().equals(sb.getClassName())) {
                                    itr.remove();
                                    break;
                                }
                            }
                        } else {
                            if (entry.getFlag() == 'U') {
                                for (ServiceBean sb : project.getServices()) {
                                    if (entry.getClassName().equals(sb.getClassName())) {
                                        sb.setDescription(entry.getDescription());
                                        break;
                                    }
                                }
                            } else {
                                ServiceBean sb = new ServiceBean();
                                sb.setClassName(entry.getClassName());
                                sb.setDescription(entry.getDescription());
                                if (project.getServices() == null)
                                    project.setServices(new ArrayList<>());
                                project.getServices().add(sb);
                            }
                        }
                    }
                    cc.beginTx();
                    if (entry.getFlag() != 'D')
                        save(cc, entry);
                    save(project);
                    cc.commitTx();
                } catch (Exception e) {
                    if (log.isErrorEnabled())
                        log.error("save", e);
                    cc.rollbackTx();
                    if (e instanceof EasyPlatformWithLabelKeyException)
                        throw new RuntimeException(I18N.getLabel(e.getMessage(), ((DaoException) e).getArgs()));
                    else
                        throw Lang.wrapThrow(e);
                } finally {
                    cc.closeTx();
                }
            }
        }

    }

    /**
     * 保存资源文件
     *
     * @param entry
     */
    private void save(CommandContext cc, CustomVo.Entry entry) {
        Object key = cc.getEntityDao().selectObject("SELECT entityId FROM " + project.getEntityTableName() + " WHERE entityId=?", entry.getClassName());
        EntityInfo entity = new EntityInfo();
        entity.setContent(entry.getContent());
        entity.setId(entry.getClassName());
        entity.setName(entry.getDescription());
        entity.setType(EntityType.RESOURCE.getName());
        entity.setSubType(SubType.CLASS.getName());
        entity.setStatus(key == null ? 'C' : 'U');
        entity.setUpdateUser(cc.getUser().getId());
        engineConfiguration.getEntityDao().updateEntity(project.getEntityTableName(), entity);
    }

    /**
     * 保存项目配置
     *
     * @param portlets
     */
    private void save(List<PortletBean> portlets) {
        ProjectBean tv = Cloner.standard().deepClone(project);
        EntityDao dao = engineConfiguration.getEntityDao();
        CommandContext cc = Contexts.getCommandContext();
        cc.beginTx();
        try {
            if (portlets != null) {
                for (PortletBean pv : portlets) {
                    for (DeviceMapBean dm : pv.getDevices()) {
                        if (dm.getLoginId() > 0)
                            dao.update("DELETE FROM ep_model_ext_info WHERE id=?", dm.getLoginId());
                        if (dm.getMainId() > 0)
                            dao.update("DELETE FROM ep_model_ext_info WHERE id=?", dm.getMainId());
                    }
                }
            }
            for (int i = 0; i < tv.getDevices().size(); i++) {
                DeviceMapBean dm = tv.getDevices().get(i);
                if (dm.getLoginId() > 0)
                    dao.update("UPDATE ep_model_ext_info SET content=? WHERE id=?", dm.getLoginPage(), dm.getLoginId());
                else
                    dm.setLoginId(dao.insert("INSERT INTO ep_model_ext_info (projectId,content) VALUES (?,?)", project.getId(), dm.getLoginPage()));
                if (dm.getMainId() > 0)
                    dao.update("UPDATE ep_model_ext_info SET content=? WHERE id=?", dm.getMainPage(), dm.getMainId());
                else
                    dm.setMainId(dao.insert("INSERT INTO ep_model_ext_info (projectId,content) VALUES (?,?)", project.getId(), dm.getMainPage()));
                dm.setLoginPage(null);
                dm.setMainPage(null);
                DeviceMapBean dmb = project.getDevices().get(i);
                dmb.setLoginId(dm.getLoginId());
                dmb.setMainId(dm.getMainId());
            }
            if (tv.getPortlets() != null) {
                for (int i = 0; i < tv.getPortlets().size(); i++) {
                    PortletBean pb = tv.getPortlets().get(i);
                    PortletBean plb = project.getPortlets().get(i);
                    for (int j = 0; j < pb.getDevices().size(); j++) {
                        DeviceMapBean dm = pb.getDevices().get(j);
                        DeviceMapBean dmb = plb.getDevices().get(j);
                        if (!Strings.isBlank(dm.getLoginPage()))
                            dm.setLoginId(dao.insert("INSERT INTO ep_model_ext_info (projectId,content) VALUES (?,?)", project.getId(), dm.getLoginPage()));
                        if (!Strings.isBlank(dm.getMainPage()))
                            dm.setMainId(dao.insert("INSERT INTO ep_model_ext_info (projectId,content) VALUES (?,?)", project.getId(), dm.getMainPage()));
                        dm.setLoginPage(null);
                        dm.setMainPage(null);
                        dmb.setLoginId(dm.getLoginId());
                        dmb.setMainId(dm.getMainId());
                    }
                }
            }
            save(tv);
            cc.commitTx();
        } catch (Exception e) {
            if (log.isErrorEnabled())
                log.error("save project:", e);
            cc.rollbackTx();
        } finally {
            cc.closeTx();
        }
    }

    /**
     * 保存项目配置
     *
     * @param pb
     */
    private void save(ProjectBean pb) {
        StringBuilder sb = new StringBuilder();
        StringOutputStream os = new StringOutputStream(sb);
        TransformerFactory.newInstance().transformToXml(pb, os);
        EntityInfo entity = new EntityInfo();
        entity.setContent(sb.toString());
        entity.setId(project.getId());
        entity.setName(project.getName());
        entity.setDescription(project.getDescription());
        entity.setStatus('U');
        entity.setUpdateUser(Contexts.getCommandContext().getUser().getId());
        engineConfiguration.getEntityDao().updateModel(entity);
    }

    @Override
    public DataSource getDataSource() {
        return (DataSource) getService(project.getBizDb());
    }

    @Override
    public DataSource getDataSource(String dbId) {
        if (Strings.isBlank(dbId))
            return (DataSource) getService(project.getBizDb());
        return (DataSource) getService(dbId);
    }

    @Override
    public int getUserCount() {
        return sessionManager.getUsers().size();
    }

    @Override
    public Map<String, String> getConfig(String name) {
        return getConfig().getOptions(name);
    }

    @Override
    public String getConfigValue(String name) {
        return getConfig().getString(name);
    }

    @Override
    public void publish(String topic, Serializable data, String... toUsers) {
        engineConfiguration.getApplicationListener().publish(project.getId(), topic, data, toUsers);
    }

    @Override
    public IResponseMessage<?> executeTask(ApplicationService service, String id, Map<String, Object> inputs) {
        CommandContext cc = createCommandContext(service);
        UserDo user = cc.getUser();
        try {
            SecurityUtils.getSubject().getSession().setTimeout(-1);
            LogManager.startUser(engineConfiguration.getLogPath(), project.getId(), user);
            LogManager.beginRequest(project.getId(), user);
            List<FieldVo> variables = null;
            if (inputs != null && !inputs.isEmpty()) {
                variables = new ArrayList<>(inputs.size());
                for (Map.Entry<String, Object> entry : inputs.entrySet())
                    variables.add(new FieldVo(entry.getKey(), FieldType.cast(entry.getValue()), entry.getValue()));
            }
            return engineConfiguration.getTaskExecuter(cc).execute(new ExecuteContext(cc, id, variables));
        } finally {
            cc.logout();
            Contexts.clear();
            LogManager.stopUser(user);
        }
    }

    @Override
    public Future<IResponseMessage<?>> asyncTask(ApplicationService service, String id, Map<String, Object> inputs) {
        return engineConfiguration.getKernelThreadPoolExecutor().submit((Callable) () -> executeTask(service, id, inputs));
    }

    @Override
    public void executeLogic(ApplicationService service, String id, Map<String, Object> inputs) {
        LogicBean lb = getEntityHandler().getEntity(id);
        if (lb == null)
            throw new RuntimeException(I18N.getLabel("entity.not.found", EntityType.LOGIC.getName(), id));
        CommandContext cc = createCommandContext(service);
        UserDo user = cc.getUser();
        try {
            LogManager.startUser(engineConfiguration.getLogPath(), project.getId(), user);
            LogManager.beginRequest(project.getId(), cc.getUser());
            Map<String, Object> systemVariables = new HashMap<String, Object>();
            RuntimeUtils.initWorkflow(systemVariables, null);
            RecordContext rc = new RecordContext(new Record(), systemVariables, new HashMap<>());
            if (inputs != null && !inputs.isEmpty()) {
                for (Map.Entry<String, Object> entry : inputs.entrySet())
                    rc.setVariable(new FieldDo(entry.getKey(), FieldType.cast(entry.getValue()), entry.getValue()));
            }
            cc.beginTx();
            String code = RuntimeUtils.eval(cc, lb, rc);
            if (!code.equals("0000"))
                throw new RuntimeException(cc.getMessage(code, rc));
            cc.commitTx();
        } catch (Exception e) {
            cc.rollbackTx();
            throw Lang.wrapThrow(e);
        } finally {
            cc.closeTx();
            cc.logout();
            Contexts.clear();
            LogManager.stopUser(user);
        }
    }

    @Override
    public void asyncLogic(ApplicationService service, String id, Map<String, Object> inputs) {
        engineConfiguration.getKernelThreadPoolExecutor().execute(() -> executeLogic(service, id, inputs));
    }

    /**
     * 通过自定义服务创建运行环境
     *
     * @param service
     * @return
     */
    private CommandContext createCommandContext(ApplicationService service) {
        CommandContext cc = new CommandContext(engineConfiguration);
        Contexts.set(CommandContext.class, cc);
        cc.setupEnv(new EnvDo(getId(),
                DeviceType.API,
                getLanguages().get(0), null, null));
        UserDo guest = new UserDo();
        guest.setId("user." + service.getId());
        guest.setName(service.getName());
        OrgDo org = new OrgDo();
        org.setId("org." + service.getId());
        org.setName(service.getDescription());
        org.setExtraInfo(new HashMap<>());
        guest.setOrg(org);
        guest.setDeviceType(DeviceType.API);
        guest.setState(StateType.START);
        guest.setIp("localhost");
        guest.setLocale(cc.getEnv().getLocale());
        guest.setRoles(new String[]{"guest"});
        cc.setUser(guest);
        return cc;
    }

    @Override
    public ICustomJob getCustomJob() {
        return customJob;
    }
}
